import URI from '../urijs/URI.js'
import errorHtml from './xwebview-errpage.js'

/**
 * webview 封装
 * 用来加载网页以及分发处理网页发送过来的消息
 * 内部解决了一些官方webview的缺陷
 * @author xueyanjun
 */
class XWebview {

	constructor(webview, options, location) {
		this._wv = webview;
		this._location = location;
		this._options = options;
		if (!options.styles.disablePlus) {
			this._onMessageCallbackPool = new Map();
			this._enablePageMessageListen();
		}
		this._enableVisibleEventListen();
		this._enableCloseEventListen();
		this._enableLoadStatusEventListen();
		this._onHideCallbackPool = new Map();
		this._onShowCallbackPool = new Map();
		this._onDestroyCallbackPool = new Map();
		this._visibleStatus = false; // false代表未显示 true代表显示
		this._loadStatus = false; // false代表内部页面未加载完成 true代表加载完成
	}

	/**
	 * 静态方法创建webview
	 * @param {String} url 新窗口加载的HTML页面地址,可支持本地地址和网络地址
	 * @param {Object} options {id:webview的唯一id，不传会自动生成，extras:扩展参数，会设置到webview中 }
	 */
	static create(url, options = {}) {

		const location = XWebview.parseURL({
			url,
			base: options.baseURL
		});
		
		options.id = options.id || 'xw-' + location.host; // webview id
		const cachedXw = XWebview.cached.get(options.id);
		if (cachedXw) {
			cachedXw.loadURL(location.href);
			return cachedXw;
		}

		// {id, styles, extras}
		options.cannotBackAction = options.cannotBackAction || 'hide'; // webview 默认不能返回后的动作，俩个值 close 和 hide
		options.styles = Object.assign({
			disablePlus: false, // 是否禁止plus调用，默认false不禁止
			popGesture: 'none',
			statusbar: {
				background: '#101010' // 状态栏颜色
			},
			decelerationRate: 0.5,
			progress: null, // 不显示加载进度条
			scalable: false, // 窗口是否可缩放，false不可双指缩放
			additionalHttpHeaders: null, // 窗口加载页面时额外添加的HTTP请求头数据 例如{Referer:'http://m.dcloud.io/}
			replacewebapi: {
				geolocation: 'auto'
			},
			render: "always", //窗口渲染模式(仅Android支持) onscreen-窗口在屏幕区可见时渲染;always-在任何时候都渲染
			plusrequire: 'ahead', //控制Webview注入5+ API时机 "ahead" - 尽量提前注入 "normal" - 页面loaded时注入； "later" - 较晚在注入，在loaded事件发生后2s再注入，"none" - 不注入，页面无法调用5+ API，不触发plusready事件。
			errorPage: '_doc/webError.html',
			// titleNView: {
			// 	autoBackButton: true,
			// 	backButton:{
			// 		title:'返回'
			// 	},
			// 	buttons:[
			// 		{type:'close',float:'right'}
			// 	],
			// 	backgroundColor: '#ffffff',
			// 	titleText: '首页',
			// 	subtitleText: '努力',
			// }
		}, options.styles);
		console.log(options)
		options.extras = options.extras || {};
		const wv = plus.webview.create(location.href, options.id, options.styles, options.extras);
		const xw = new XWebview(wv, options, location);

		XWebview.cached.set(options.id, xw);

		return xw;
	}
	
	static parseURL({base,url}) {
        if (url.startsWith('_doc')) {
            return {
                protocol: 'file',
                host: private_doc.replace('file://', ''),
                origin: private_doc,
                href: url.replace('_doc', private_doc)
            }
        }

        if (url.startsWith('file:///')) {
            return {
                protocol: 'file',
                host: private_doc.replace('file://', ''),
                origin: private_doc,
                href: url
            }
        }

        const uri = URI(url);
        uri.addQuery("xw_v", 1); // 加入webview标识，方便h5页面获取是否运行在webview环境内
        const lo = {
            protocol: uri.protocol(),
            href: uri.href(),
            origin: uri.origin(),
            host: uri.host(),
            path: uri.path(),
            query: uri.query(),
            hash: uri.hash()
        }
        lo.base = base || lo.origin;
        lo.basePath = URI(lo.base).path();
        if (lo.query !== 'xw_v=1') {
            // 将query解析成obj以发送给webview内部的网页
            lo.qobj = URI.parseQuery(lo.query);
        }
        return lo;
   }

	static installPlugins(...plugins) {
		XWebview._plugins = XWebview._plugins || [];
		for (let i = 0; i < plugins.length; i++) {
			const plugin = plugins[i];
			if (plugin.$onBridgeInstall == null || !(typeof plugin.$onBridgeInstall === 'function')) {
				throw new Error('插件的$onBridgeInstall属性不能为空且必须是一个function');
			}
			plugin.$onBridgeInstall(this);
			if (plugin.$onBridgeMessage == null || !(typeof plugin.$onBridgeMessage === 'function')) {
				throw new Error('插件的$onBridgeMessage属性不能为空且必须是一个function');
			}
			if (plugin.$bridgeMessageTypes == null || plugin.$bridgeMessageTypes.length === 0) {
				throw new Error('插件的$bridgeMessageTypes属性不能为空且必须是一个array');
			}
			XWebview._plugins.push(plugin);
		}
	}

	/**
	 * 内部方法，生成一个不重复的随机id
	 */
	_genNonDuplicateId() {
		return Number(Math.random().toString().substr(2, 8)).toString(36) + Date.now().toString(36);
	}

	/**
	 * 内部函数，开启网页消息监听
	 */
	_enablePageMessageListen() {
		// 重写网页消息分发函数
		this.dispatchPageMessage = function(msg) {
			const _this = this;
			const action = msg.a;
			const data = msg.d;

			if (action === 'cus') {
				_this._onMessageCallbackPool.forEach(fn => {
					fn(data);
				})
				return null;
			}

			if (action === 'wvapi') {
				const method = data.me;
				if (method === 'back') {
					_this.pageHistoryBack(data.ani, data.duration, data.topAct);
				}
				if (method === 'close') {
					_this.destroy(data.ani, data.duration);
				}
				if (method === 'hide') {
					_this.hide(data.ani, data.duration);
				}
				if (method === 'reload') {
					_this.reload(data.force);
				}
				return null;
			}

			for (let plugin of XWebview._plugins) {
				if (plugin && plugin.$bridgeMessageTypes.includes(action)) {
					Promise.resolve().then(() => {
						plugin.$onBridgeMessage(data, _this);
					});
					return null;
				}
			}
			return null;
		}

		// 下面拦截消息方式仍然可以使用，但使用过程中发现了会破坏开发环境时的网页热更新机制
		// 这里仍然保留的原因时为了防止某些手机中的webview可能不支持postMessageToUniNView函数
		// 利用拦截url来实现网页给app发送消息
		this._wv.overrideUrlLoading({
			effect: 'instant',
			mode: 'reject',
			match: 'k://xe.*'
		}, (e) => {
			const url = e.url;
			const param = url.replace('k://xe?p=', '');
			const obj = JSON.parse(decodeURIComponent(param));
			return this.dispatchPageMessage(obj);
		});
	}

	/**
	 * 网页消息分发
	 * @param {Object} msg 消息
	 */
	dispatchPageMessage(msg) {/*空函数占位，当开启监听网页消息时会重写该方法*/}

	/**
	 * 内部函数，webview显示隐藏事件监听
	 */
	_enableVisibleEventListen() {
		const _this = this;
		// webview 隐藏时遍历调用注册的隐藏监听函数
		_this._viewHideEventCall = function() {
			if (_this._visibleStatus) {
				_this._visibleStatus = false;
				_this._onHideCallbackPool.forEach(fn => {
					fn(_this._location);
				});
			}
		}
		// webview 显示时遍历调用注册的显示监听函数
		_this._viewShowEventCall = function() {
			if (!_this._visibleStatus) {
				_this._visibleStatus = true;
				_this._onShowCallbackPool.forEach(fn => {
					fn(_this._location);
				});
			}
		}
		_this._wv.addEventListener('hide', _this._viewHideEventCall, false);
		_this._wv.addEventListener('show', _this._viewShowEventCall, false);
	}

	_enableLoadStatusEventListen() {
		const _this = this;
		_this._loadStatusErrorEventCall = function() {
			_this._loadStatus = false;
		}
		_this._loadStatusSuccessEventCall = function(e) {
			_this._loadStatus = true;
		}
		_this._wv.addEventListener('loaded', _this._loadStatusSuccessEventCall, false);
		_this._wv.addEventListener('error', _this._loadStatusErrorEventCall, false);
	}

	/**
	 * 内部函数，webview销毁并关闭事件监听
	 */
	_enableCloseEventListen() {
		const _this = this;
		// 关闭webview时并移除各种监听器
		_this._viewCloseEventCall = function() {
			XWebview.cached.delete(_this._location.origin);
			_this._wv.removeEventListener('hide', this._viewHideEventCall);
			_this._wv.removeEventListener('show', this._viewShowEventCall);
			_this._wv.removeEventListener('loaded', _this._loadStatusSuccessEventCall);
			_this._wv.removeEventListener('error', _this._loadStatusErrorEventCall);
			_this._wv.removeEventListener('close', _this._viewCloseEventCall);
			_this._onDestroyCallbackPool.forEach(fn => {
				fn(_this._location);
			});
			_this._wv = null;
			_this._onShowCallbackPool = null;
			_this._onHideCallbackPool = null;
			_this._onMessageCallbackPool = null;
			_this._onDestroyCallbackPool = null;
		}
		_this._wv.addEventListener('close', _this._viewCloseEventCall);
	}

	_postMsg(msg) {
		let _msg = msg;
		if (typeof msg === 'object') {
			_msg = JSON.stringify(msg);
		}
		// 利用evalJS来实现app给网页发送消息
		this._wv.evalJS(`window.xwbridge._onMsg(${_msg});`);
	}


	/**
	 * webview 内部页面返回api
	 * 调用该方法会自动判断webview浏览器的访问历史是否还可以回退，可以回退是回退，不可以回退时将关闭webview
	 */
	pageHistoryBack(ani = 'pop-out', duration = 200, cannotBackAction) {
		const _this = this;
		const xw = _this._wv;
		if (xw.isVisible()) {
			xw.canBack(function(event) {
				if (event.canBack) {
					xw.back();
				} else {
					cannotBackAction = cannotBackAction || _this._options.cannotBackAction;
					if (cannotBackAction === 'close') { // close or hide
						_this.destroy(ani, duration);
					} else {
						_this.hide(ani, duration);
					}
				}
			});
		}
	}

    parseURL({base,url}){
        return XWebview.parseURL({base,url});
    }
	/**
	 * 加载url地址，相对路径地址
	 * @param {string} url 相对路径地址
	 * @param {Object} httpHeaders 加载时需要额外携带的http header
	 */
	loadURL(url, httpHeaders) {
		if (!url) {
			return this;
		}
		if (url.startsWith('http') && !url.startsWith(this._location.origin)) {
			throw new Error('出于安全性考虑，暂不支持更换域名，请考虑重新创建一个webview对象')
		}
		let location = this.parseURL({url});
		if (location.protocol !== '') { // 绝对路径地址
			if (location.protocol === 'file') {
				this._location.host = location.host;
				this._location.origin = location.origin;
			}
			this._location.href = location.href;
		} else {
			// 相对路径地址
			location = this.parseURL({
				url: this._location.base + location.href
			});
			this._location.href = location.href;
		}
		this._location.query = location.query;
		this._location.hash = location.hash;
		this._location.path = location.path;
		this._location.qobj = location.qobj;

		if (!this._options.styles.disablePlus) {
			// 开启plus支持的网页是公司的项目网页
			if (this._loadStatus) { // 内部已加载成功，只主动通知网页自行处理
				this._postMsg({
					a: 'event',
					d: {
						me: 'loadURL',
						location: this._location
					}
				});
			} else { // 未加载成功，切换地址重新加载
				this.clearPageStatus();
				this._wv.loadURL(this._location.href, httpHeaders);
			}
		} else {
			// 其他网页 直接加载
			this._wv.loadURL(this._location.href, httpHeaders);
		}
		return this;
	}

	/**
	 * 重新加载
	 * @param {Boolean} force 是否强制加载，true代表强制及不使用缓存，重新从URL地址加载所有页面内容
	 */
	reload(force = false) {
		this._wv.reload(force);
		return this;
	}

	/**
	 * 清除网页状态
	 * 包括历史记录和当前页面清空
	 */
	clearPageStatus() {
		this._wv.clear();
		return this;
	}

	/**
	 * 获取webview唯一id
	 */
	getId() {
		return this._wv.id;
	}

	/**
	 * 显示Webview
	 * @param {Object} aniShow 显示Webview窗口动画效果
	 * @param {Object} duration 显示Webview窗口的动画持续时间
	 * @param {Object} extras 显示Webview窗口扩展参数 可用于指定Webview窗口动画是否使用图片加速
	 */
	show(aniShow = 'pop-in', duration = 200, extras) {
		if (this._wv.isPause()) {
			this._wv.resume();
		}
		let time = 100;
		if (envSysteminfo.platform === 'android') {
			try {
				const osVersion = parseFloat(envSysteminfo.osVersion)
				if (osVersion === 10) {
					time = 200;
				} else if (osVersion <= 9) {
					time = 300;
				}
			} catch (err) {
				console.error('webview show 异常 ===>', err)
			}
		}
		setTimeout(() => {
			this._wv.show(aniShow, duration, extras);
		}, time);
		return this;
	}

	/**
	 * 隐藏Webview
	 * @param {Object} aniShow 显示Webview窗口动画效果
	 * @param {Object} duration 显示Webview窗口的动画持续时间
	 * @param {Object} extras 显示Webview窗口扩展参数 可用于指定Webview窗口动画是否使用图片加速
	 */
	hide(aniHide = 'pop-out', duration = 200, extras) {
		this._wv.hide(aniHide, duration, extras);
		this._wv.pause();
		return this;
	}

	/**
	 * 关闭并销毁Webview
	 * @param {Object} aniClose 关闭Webview窗口动画效果
	 * @param {Object} duration 关闭Webview窗口的动画持续时间
	 * @param {Object} extras 关闭Webview窗口扩展参数 可用于指定Webview窗口动画是否使用图片加速
	 */
	destroy(aniClose = 'pop-out', duration = 200, extras) {
		this._wv.close(aniClose, duration, extras);
	}


	/**
	 * 向网页发送消息
	 * @param {Object} msg
	 */
	postMessage(msg) {
		const _msg = {
			a: 'cus',
			d: msg
		}
		// 利用evalJS来实现app给网页发送消息
		this._postMsg(_msg);
		return this;
	}

	/**
	 * 注册网页消息监听器
	 * @param {Function} fn 监听回调函数，格式如fn(msg)，msg代表网页传递过来的消息体
	 */
	onMessage(fn) {
		if (fn == null || typeof fn !== 'function') {
			throw new Error('注册webview page消息监听参数必须是一个function');
		}
		if (fn.xwfnId) {
			return;
		}
		fn.xwfnId = this._genNonDuplicateId();
		this._onMessageCallbackPool.set(fn.xwfnId, fn);
		return this;
	}

	/**
	 * 移除网页消息监听器
	 * @param {Function} fn 注册时传入的监听回调函数
	 */
	offMessage(fn) {
		if (fn == null || typeof fn !== 'function') {
			throw new Error('移除webview page消息监听参数必须是一个function');
		}
		this._onMessageCallbackPool.forEach((pfn) => {
			if (pfn === fn || pfn.xwfnId === fn.xwfnId) {
				this._onMessageCallbackPool.delete(pfn.xwfnId);
				fn.xwfnId = void 0;
			}
		});
		return this;
	}

	/**
	 * 注册webview隐藏监听器
	 * @param {Object} fn 监听回调函数 格式如fn(lo)，lo代表当前webview载入页面的location
	 */
	onHide(fn) {
		if (fn == null || typeof fn !== 'function') {
			throw new Error('注册webview 隐藏监听参数必须是一个function');
		}
		if (fn.xwfnId) {
			return;
		}
		fn.xwfnId = this._genNonDuplicateId();
		this._onHideCallbackPool.set(fn.xwfnId, fn);
		return this;
	}

	/**
	 * 移除webview隐藏监听器
	 * @param {Function} fn 注册时传入的监听回调函数
	 */
	offHide(fn) {
		if (fn == null || typeof fn !== 'function') {
			throw new Error('移除webview 隐藏监听参数必须是一个function');
		}
		this._onHideCallbackPool.forEach((pfn) => {
			if (pfn === fn || pfn.xwfnId === fn.xwfnId) {
				this._onHideCallbackPool.delete(pfn.xwfnId);
				fn.xwfnId = void 0;
			}
		});
		return this;
	}

	/**
	 * 注册webview显示监听器
	 * @param {Object} fn 监听回调函数 格式如fn(lo)，lo代表当前webview载入页面的location
	 */
	onShow(fn) {
		if (fn == null || typeof fn !== 'function') {
			throw new Error('注册webview 显示监听参数必须是一个function');
		}
		if (fn.xwfnId) {
			return;
		}
		fn.xwfnId = this._genNonDuplicateId();
		this._onShowCallbackPool.set(fn.xwfnId, fn);
		return this;
	}

	/**
	 * 移除webview显示监听器
	 * @param {Function} fn 注册时传入的监听回调函数
	 */
	offShow(fn) {
		if (fn == null || typeof fn !== 'function') {
			throw new Error('移除webview 显示监听参数必须是一个function');
		}
		this._onShowCallbackPool.forEach((pfn) => {
			if (pfn === fn || pfn.xwfnId === fn.xwfnId) {
				this._onShowCallbackPool.delete(pfn.xwfnId);
				fn.xwfnId = void 0;
			}
		});
		return this;
	}

	/**
	 * 注册webview销毁监听器
	 * @param {Object} fn 监听回调函数 格式如fn(lo)，lo代表当前webview载入页面的location
	 */
	onDestroy(fn) {
		if (fn == null || typeof fn !== 'function') {
			throw new Error('注册webview 销毁监听参数必须是一个function');
		}
		if (fn.xwfnId) {
			return;
		}
		fn.xwfnId = this._genNonDuplicateId();
		this._onDestroyCallbackPool.set(fn.xwfnId, fn);
		return this;
	}

	/**
	 * 移除webview销毁监听器
	 * @param {Object} fn 注册时传入的监听回调函数
	 */
	offDestroy(fn) {
		if (fn == null || typeof fn !== 'function') {
			throw new Error('移除webview 销毁监听参数必须是一个function');
		}
		this._onDestroyCallbackPool.forEach((pfn) => {
			if (pfn === fn || pfn.xwfnId === fn.xwfnId) {
				this._onDestroyCallbackPool.delete(pfn.xwfnId);
				fn.xwfnId = void 0;
			}
		});
		return this;
	}

	/**
	 * 判断webview是否已经关闭且销毁
	 * @return {boolean} true代表已销毁，false代表没有销毁
	 */
	isDestroyed() {
		return this._wv == null;
	}

	/**
	 * 判断webview当前是否可视
	 * @return {boolean} true代表可视，false代表不可视
	 */
	isVisible() {
		return this._visibleStatus;
	}

	/**
	 * 从另一个xw对象复制属性到本xw中
	 * @param {XWebview} xw 另一个xw
	 */
	copyFrom(xw) {
		this._wv = xw._wv;
		this._location = xw._location;
		this._options = xw._options;
		this._onHideCallbackPool = xw._onHideCallbackPool;
		this._onShowCallbackPool = xw._onShowCallbackPool;
		this._onDestroyCallbackPool = xw._onDestroyCallbackPool;
		this._visibleStatus = xw._visibleStatus;
		this._loadStatus = xw._loadStatus;
		return this;
	}

}

const nativeToast = plus.nativeUI.toast;
const nativeQuit = plus.runtime.quit;
const envSysteminfo = uni.getSystemInfoSync();
uni.setStorage({
	key: 'env:systeminfo',
	data: JSON.stringify(envSysteminfo)
});
let private_doc = null; //应用私有文档目录;仅应用自身可读写
plus.io.requestFileSystem(plus.io.PRIVATE_DOC, function(fs) {
	private_doc = 'file://' + fs.root.fullPath;
	fs.root.getFile('webError.html', {
		create: true // 文件不存在则创建
	}, fileEntry => {
		// 文件在手机中的路径
		fileEntry.createWriter(writer => {
			// 写入文件成功完成的回调函数
			writer.onwrite = e => {};
			// 写入数据
			writer.write(errorHtml);
		})
	}, e => {
		console.log("getFile failed: " + e.message);
	});
}, function(e) {
	console.log("Request file system failed: " + e.message);
});

XWebview.cached = new Map();

// 监听全局消息来分发网页传输过来的内容
plus.globalEvent.addEventListener('plusMessage', (message) => {
	const data = message.data;
	if (data.type !== 'xw-msg') {
		return;
	}
	const xwId = data.xwId;
	const xw = XWebview.cached.get(xwId);
	xw.dispatchPageMessage(data.args);
})

plus.runtime.quit = function() {
	if (XWebview.cached && XWebview.cached.size > 0) {
		for (let cache of XWebview.cached) {
			const xw = cache[1];
			if (xw && xw.isVisible()) {
				return false;
			}
		}
	}
	nativeQuit();
}

plus.nativeUI.toast = function(str, styles) {
	if (!XWebview.cached || XWebview.cached.size === 0) {
		nativeToast(str, styles);
		return;
	}
	if (str === '再按一次退出应用' || str === '再按一次退出應用' || str === 'Press back button again to exit') {
		for (let cache of XWebview.cached) {
			const xw = cache[1];
			if (xw && xw.isVisible()) {
				return false;
			}
		}
	}
}

uni.addInterceptor('navigateBack', {
	invoke(e) {
		if (e.from === 'backbutton') {
			if (!XWebview.cached && XWebview.cached.size === 0) {
				return true;
			}
			const currentlyVisibleWv = plus.webview.getTopWebview();
			for (let cache of XWebview.cached) {
				const xw = cache[1];
				if (currentlyVisibleWv && xw && currentlyVisibleWv.id === xw.getId()) {
					xw.pageHistoryBack();
					return false;
				}
			}
		}
		return true;
	},
	complete() {
		if (!XWebview.cached && XWebview.cached.size === 0) {
			return true;
		}
		setTimeout(() => {
			const currentlyVisibleWv = plus.webview.getTopWebview();
			for (let cache of XWebview.cached) {
				const xw = cache[1];
				if (currentlyVisibleWv && xw && currentlyVisibleWv.id === xw.getId()) {
					xw._viewShowEventCall();
				}
			}
		}, 400);
	}
});

uni.addInterceptor('navigateTo', {
	invoke() {
		if (!XWebview.cached && XWebview.cached.size === 0) {
			return true;
		}
		const currentlyVisibleWv = plus.webview.getTopWebview();
		for (let cache of XWebview.cached) {
			const xw = cache[1];
			if (currentlyVisibleWv && xw && currentlyVisibleWv.id === xw.getId()) {
				xw._viewHideEventCall();
			}
		}
	}
});


uni.addInterceptor('reLaunch', {
	invoke() {
		if (!XWebview.cached && XWebview.cached.size === 0) {
			return true;
		}
		for (let cache of XWebview.cached) {
			const xw = cache[1];
			xw.hide('none');
		}
	}
});


export default XWebview;
